package yawning.mew.character;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import yawning.mew.util.URLFetcher;

public final class Glyph {
	public enum Type {
		PRIME, MAJOR, MINOR
	}

	public static Glyph getByItemId(int id) {
		if (sGlyphsByGlyphId.size() == 0) loadGlyphs();
		return sGlyphsByItemId.get(id);
	}

	// FIXME: glyphID seems to be deprecated, so we could probably get rid of it.
	public static Glyph getById(int id) {
		if (sGlyphsByGlyphId.size() == 0) loadGlyphs();
		return sGlyphsByGlyphId.get(id);
	}

	public static Glyph[] getForClass(Clazz c) {
		if (sGlyphsByGlyphId.size() == 0) loadGlyphs();
		return sClassGlyphs.get(c).toArray(new Glyph[0]);
	}

	public static Glyph getByName(String name) {
		if (sGlyphsByGlyphId.size() == 0) loadGlyphs();
		name = name.toLowerCase();
		if (name.startsWith(GLYPH_OF)) name = name.substring(GLYPH_OF.length());
		Glyph g = sGlyphsByName.get(name);
		if (g == null) {
			// Check the rename table to see if the Glyph was changed on PTR.
			if (sGlyphRename.containsKey(name)) {
				g = sGlyphsByName.get(sGlyphRename.get(name));
				if (g != null)
					return g;
			}
			System.out.println("Glyph " + name + " not found!");
		}
		return g;
	}

	@Override
	public String toString() {
		return mName;
	}

	public Clazz getClazz() {
		return mClass;
	}

	public int getItemId() {
		return mItemId;
	}

	public Type getType() {
		return mType;
	}

	public int getGlyphId() {
		return mGlyphId;
	}

	public String getDescription() {
		return mDescription;
	}

	private String mName;
	private Clazz mClass;
	private int mItemId;
	private Type mType;
	private int mGlyphId;
	private String mDescription;

	private static final String GLYPHS_FILE = "/META-INF/glyphs.xml";

	private static final Map<Integer,Glyph> sGlyphsByGlyphId = new HashMap<Integer,Glyph>();
	private static final Map<Integer,Glyph> sGlyphsByItemId = new HashMap<Integer,Glyph>();
	private static final Map<Clazz,List<Glyph>> sClassGlyphs = new HashMap<Clazz,List<Glyph>>();
	private static final Map<String,Glyph> sGlyphsByName = new HashMap<String,Glyph>();
	private static final Map<String,String> sGlyphRename;
	static {
		Map<String,String> renamedGlyphs = new HashMap<String,String>();
		renamedGlyphs.put("shred", "bloodletting");
		sGlyphRename = Collections.unmodifiableMap(renamedGlyphs);
	}

	private static final String GLYPH_OF = "glyph of ";

	private static final String ATTR_NAME = "name";
	private static final String ATTR_CLASS = "class";
	private static final String ATTR_ITEMID = "itemId";
	private static final String ATTR_TYPE = "type";
	private static final String ATTR_GLYPHID = "glyphId";
	private static final String ATTR_DESC = "description";

	private static final String ELEMENT_ROOT = "glyphs";
	private static final String ELEMENT_GLYPH = "glyph";

	public static void main(String args[]) {
		reloadGlyphs();
		Glyph[] glyphs = Clazz.DRUID.getGlyphs();
		for (Glyph g : glyphs) System.out.println(g);
		System.out.println(Glyph.getByName("berserk"));
		System.out.println(Glyph.getByName("gLyPH of BERserk"));
	}

	private static void loadGlyphs() {
		synchronized (sGlyphsByGlyphId) {
			if (sGlyphsByGlyphId.size() == 0) {
				try {
					SAXBuilder builder = new SAXBuilder();
					InputStream is = Glyph.class.getResourceAsStream(GLYPHS_FILE);
					Document doc = builder.build(is);
					Element root = doc.getRootElement();
					@SuppressWarnings("unchecked") List<Element> glyphs = root.getChildren(ELEMENT_GLYPH);
					for (Element e : glyphs) {
						Glyph g = new Glyph();
						g.mName = e.getAttributeValue(ATTR_NAME);
						g.mClass = Clazz.get(e.getAttributeValue(ATTR_CLASS));
						g.mItemId = Integer.parseInt(e.getAttributeValue(ATTR_ITEMID));
						g.mType = Type.valueOf(e.getAttributeValue(ATTR_TYPE));
						g.mGlyphId = Integer.parseInt(e.getAttributeValue(ATTR_GLYPHID));
						g.mDescription = e.getAttributeValue(ATTR_DESC);

						sGlyphsByGlyphId.put(g.mGlyphId, g);
						sGlyphsByItemId.put(g.mItemId, g);
						if (!sClassGlyphs.containsKey(g.mClass)) sClassGlyphs.put(g.mClass, new ArrayList<Glyph>());
						sClassGlyphs.get(g.mClass).add(g);
						String name = g.mName.toLowerCase();
						if (name.startsWith(GLYPH_OF)) name = name.substring(GLYPH_OF.length());
						sGlyphsByName.put(name, g);
					}
				}
				catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private static void reloadGlyphs() {
		try {
			Element root = new Element(ELEMENT_ROOT);

			for (Clazz c : Clazz.values()) {
				System.out.println("Scraping glyphs for class " + c);

				URL url = new URL("http://db.mmo-champion.com/items/16/" + c.mCid);
				String glyphListStr = URLFetcher.getInstance().fetchAsString(url);

				Matcher m = Pattern.compile("var search_results_item = \\[(.+?)\\\n]\n", Pattern.DOTALL).matcher(glyphListStr);
				m.find();

				// Ugh, this isn't actually JSON, so all the quotes are fucked up.
				String classListStr = m.group(1).replace('\'', '\"').replace("\"s ", "'s ");

				JSONArray jsa = (JSONArray) JSONValue.parse("[ " + classListStr + " ]");
				int nrGlyphs = jsa.size();
				for (int i = 0; i < nrGlyphs; i++) {
					JSONObject jso = (JSONObject) jsa.get(i);

					String name = (String) jso.get("name");
					if (!name.startsWith("Glyph of ")) {
						System.out.println("Skipping glyph named: " + name);
						continue;
					}

					System.out.println("Querying " + name + "...");
					Element glyph = new Element(ELEMENT_GLYPH);
					glyph.setAttribute(ATTR_NAME, name);

					String icon = (String) jso.get("icon");
					if (icon.contains(Type.PRIME.toString().toLowerCase())) glyph.setAttribute(ATTR_TYPE, Type.PRIME.toString());
					else if (icon.contains(Type.MAJOR.toString().toLowerCase())) glyph.setAttribute(ATTR_TYPE, Type.MAJOR.toString());
					else if (icon.contains(Type.MINOR.toString().toLowerCase())) glyph.setAttribute(ATTR_TYPE, Type.MINOR.toString());
					else {
						System.out.println("Glyph has no type: " + name);
						continue;
					}

					glyph.setAttribute(ATTR_CLASS, c.toString());;

					long itemId = (Long) jso.get("id");
					glyph.setAttribute(ATTR_ITEMID, Long.toString(itemId));

					url = new URL("http://db.mmo-champion.com/i/" + itemId);
					String itemStr = URLFetcher.getInstance().fetchAsString(url);

					// <a href="/g/164/glyph-of-mangle/">Permanently teaches you this glyph.\n\nIncreases the damage done by Mangle by 10%.</a>
				    m = Pattern.compile("<a href=\"/g/(\\d+)/.+?/\">Permanently teaches you this glyph\\.\n\n(.+?)</a>").matcher(itemStr);
				    if (!m.find()) {
				    	// Ugh, there's some duplicates in the list.
				    	System.out.println("Glyph has no description/id: " + name);
				    	continue;
				    }
			    	glyph.setAttribute(ATTR_GLYPHID, m.group(1));
			    	glyph.setAttribute(ATTR_DESC, m.group(2));
			    	root.addContent(glyph);
				}
			}

			XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
//			out.output(new Document(root), new FileOutputStream(GLYPHS_FILE));
			out.output(new Document(root), new FileOutputStream("glyphs.xml"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
